/*
 * Copyright Hundsun Technologies Inc. All Rights Reserved.
 */

package checker

import (
	"context"
	"errors"
	"fmt"
	"time"

	"hundsun.com/hsl/hschain/common/types"

	"hundsun.com/hsl/hschain/common/config"
	"hundsun.com/hsl/hschain/common/log/logging"
	"hundsun.com/hsl/hschain/common/util/pb"
	consensuscomm "hundsun.com/hsl/hschain/consensus/common"
	consensustypes "hundsun.com/hsl/hschain/consensus/types"
	"hundsun.com/hsl/hschain/module/common/utils"
	"hundsun.com/hsl/hschain/protos"
	pbledger "hundsun.com/hsl/hschain/protos/ledger"
)

var (
	// configParamBlockCheckInterval 校验区块间隔时间（单位:ms）
	configParamBlockCheckInterval = &utils.IntModuleConfigParam{Name: "blockCheckInterval", DefaultValue: 5}
	// configParamBlockFetchInterval 主动拉取区块间隔时间，即多久没收到区块则主动通知p2p去拉取（单位:ms）
	configParamBlockFetchInterval = &utils.IntModuleConfigParam{Name: "blockFetchInterval", DefaultValue: 3000}
	// configParamPendingBlockPoolFetchSize 从pendingBlockPool批量获取区块时单次请求能获取区块的数量
	configParamPendingBlockPoolFetchSize = &utils.IntModuleConfigParam{Name: "pendingBlockPoolFetchSize", DefaultValue: 10}
)

// NewChecker 新建一个checker模块
func NewChecker(client *consensuscomm.BaseClient) *SoloChecker {
	ck := &SoloChecker{baseClient: client}

	return ck
}

// SoloChecker solo验证模块
type SoloChecker struct {
	baseClient *consensuscomm.BaseClient
	Log        logging.Log
	RootCtx    context.Context
}

// Start 启动
func (ck *SoloChecker) Start(rootCtx context.Context, cfg *config.ModuleConfig, log logging.Log) {
	ck.RootCtx, _ = context.WithCancel(rootCtx)
	ck.Log = log

	checkerConfig := &consensustypes.CheckerConfig{
		ModuleLog:                 ck.Log,
		BlockCheckInterval:        configParamBlockCheckInterval.GetValue(cfg, ck.Log),
		BlockFetchInterval:        configParamBlockFetchInterval.GetValue(cfg, ck.Log),
		PendingBlockPoolFetchSize: configParamPendingBlockPoolFetchSize.GetValue(cfg, ck.Log),
	}

	err := ck.Init(checkerConfig)
	if err != nil {
		ck.Log.Errorf("init checker failed, err: %s", err.Error())
		return
	}
	ck.FetchAndCheckBlocks()
}

// FetchAndCheckBlocks 拉取验证区块
func (ck *SoloChecker) FetchAndCheckBlocks() {
	startedSignal := make(chan struct{})
	go ck.baseClient.Run(ck.RootCtx, startedSignal)
	<-startedSignal
}

// Init 初始化
func (ck *SoloChecker) Init(conf *consensustypes.CheckerConfig) error {
	conf.BlockCreatorValidator = ck.createBlockCreatorValidator()
	return ck.baseClient.InitChecker(conf)
}

func (ck *SoloChecker) createBlockCreatorValidator() consensustypes.BlockCreatorValidator {
	return func(blk *pbledger.Block) (err error) {
		log := pb.GetBlockLog(ck.Log, blk)
		start := time.Now()
		err = ck.validateBlockCreator(blk)
		log.WithCostField(start).Debug("block validated")
		if err != nil {
			return err
		}

		return nil
	}
}

// validateBlockCreator 验证区块创建者是否是创世块中配置的，背书签名是否正确
func (ck *SoloChecker) validateBlockCreator(block *pbledger.Block) error {
	//1.校验区块签名基本格式
	if block.Header.Signature == nil || len(block.Header.Signature.Signature) == 0 || len(block.Header.Signature.PublicKey) == 0 {
		return errors.New("signature content format error")
	}

	//2.校验块签名
	blockCreator := block.Header.Signature.GetAddress()
	var minerAddress protos.Address
	consensusPayload := ck.baseClient.GetCurParam().GetConsensus().GetSoloPayload()
	if consensusPayload != nil {
		minerAddress = protos.Address(consensusPayload.GetAddress())
	} else {
		panic(types.ErrInvalidParam)
	}
	if blockCreator != minerAddress {
		return fmt.Errorf("invalid creator '%s' of block[%d]", blockCreator, block.Header.Number)
	}
	if !block.Verify() {
		return fmt.Errorf("failed to verify signature fo block '%d'", block.Header.Number)
	}
	return nil
}
